#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <chrono>

#include "DateTimeExcep.hpp"

namespace header
{
namespace details
{

// format char
constexpr char YearFormat  = 'Y';
constexpr char MonthFormat = 'M';
constexpr char DayFormat   = 'D';
// constexpr char EscapeCh    = '&';        // ! 目前不提供转义字符

using AbbreFullPair = std::pair<std::string, std::string>;      // 缩写全称对

const std::string TLDate = "1582-10-15";

const std::vector<int> DayPerMonth{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
const std::vector<AbbreFullPair> MonthAbbreFull{ 
    {"Jan.", "January"}, {"Feb.", "February"}, {"Mar.", "March"}, {"Apr.", "April"}, 
    {"May.", "May"}, {"Jun.", "June"}, {"Jul.", "July"}, {"Aug.", "August"}, 
    {"Sept.", "September"}, {"Oct.", "October"}, {"Nov.", "November"}, {"Dec.", "December"} 
};
const std::vector<AbbreFullPair> DayAbbreFull{
    {"Sun.", "Sunday"}, {"Mon.", "Monday"}, {"Tues.", "Tuesday"}, {"Wed.", "Wednesday"}, 
    {"Thur.", "Thursday"}, {"Fri.", "Friday"}, {"Sat.", "Saturday"}
};

inline void get_now_date(int& year, int& month, int& day)
{
    auto now = std::chrono::system_clock::now();
    std::time_t now_time_t = std::chrono::system_clock::to_time_t(now);
    std::tm* now_tm = std::localtime(&now_time_t);
    year = now_tm->tm_year + 1900; // tm_year 是从 1900 年开始计数的
    month = now_tm->tm_mon + 1;    // tm_mon 是从 0 开始计数的
    day = now_tm->tm_mday;
}

inline bool is_leap_year(int year)
{
    if ((year % 4 == 0) && (year % 100 != 0)) return true;
    else if (year % 400 == 0) return true;
    else if ((year % 3200 == 0) && (year % 172800 == 0)) return true;
    return false; 
}

// @return 日期是否大于 1582年10月15日
inline bool more_than_TLdate(int year, int month, int day)
{
    return true;        // ! 目前不做判断
}

inline int cen(int year)
{
    return year / 100;
}

// @brief 泰勒公式计算周几，仅适用 1582年10月15日
// 0 -> sunday
inline int cal_weekday(int year, int month, int day)
{
    if (more_than_TLdate(year, month, day)) {
        if (month < 3) {
            --year;
            month += 12;
        }
        int y = year % 100;
        int w = (y + y / 4 + cen(year) / 4 - 2 * cen(year) + 13 * (month + 1) / 5 + day - 1);
        if (w < 0) w = 7 - (-w) % 7;
        else w = w % 7;
        return w;
    }
    return -1;
}

// ============================ parse year format ========================================
inline void parse_year_format2(int year, std::string& res)
{
    res.push_back(static_cast<char>((year / 10) % 10 + '0'));
    res.push_back(static_cast<char>((year % 10) + '0'));
}

inline void parse_year_format4(int year, std::string& res)
{
    res += std::to_string(year);
}

/**
 * @brief 从 pos 开始解析 format，直到下一字符不是 YearFormat
 * @param pos the start index of parsing format, return the end index of parsing format
 * @param res 解析的结果添加到 res 末尾
 */
inline void parse_year_format(int& pos, const std::string& format, int year, std::string& res)
{
    auto len = format.size();
    int cnt = 0;
    for (; pos < len; ++pos) {
        if (format[pos] != details::YearFormat) {
            --pos; 
            break;
        }
        ++cnt;
        if (cnt == 4) {
            parse_year_format4(year, res);
            cnt = 0;
        }
    }
    if (cnt == 2) {
        cnt = 0;
        parse_year_format2(year, res);
    }
    if (cnt != 0) throw DateTimeExcep{ "illegal date-format: wrong YearFormat count" };
}

// ============================ parse month format ===============================
inline void parse_month_format1(int month, std::string& res)
{
    res += std::to_string(month);
}

inline void parse_month_format2(int month, std::string& res)
{
    res.push_back(static_cast<char>(month / 10 + '0'));
    res.push_back(static_cast<char>(month % 10 + '0'));
}

inline void parse_month_format3(int month, std::string& res)
{
    res += MonthAbbreFull[month - 1].first;
}

inline void parse_month_format4(int month, std::string& res)
{
    res += MonthAbbreFull[month - 1].second;    
}

/**
 * @brief 从 pos 开始解析 format，直到下一字符不是 MonthFormat
 * @param pos the start index of parsing format, return the end index of parsing format
 * @param res 解析的结果添加到 res 末尾
 */
inline void parse_month_format(int& pos, const std::string& format, int month, std::string& res)
{
    auto len = format.size();
    int cnt = 0;
    for (; pos < len; ++pos) {
        if (format[pos] != details::MonthFormat) {
            --pos; 
            break;
        }
        ++cnt;
        if (cnt == 4) {
            parse_month_format4(month, res);
            cnt = 0;
        }
    }
    if (cnt == 3) {
        cnt = 0;
        parse_month_format3(month, res);
    }
    else if (cnt == 2) {
        cnt = 0;
        parse_month_format2(month, res);
    }
    else if (cnt == 1) {
        cnt = 0;
        parse_month_format1(month, res);
    }
    if (cnt != 0) throw DateTimeExcep{ "illegal date-format: wrong MonthFormat count" };
}

// =============================== parse day format ===============================
inline void parse_day_format1(int day, std::string& res)
{
    res += std::to_string(day);
}

inline void parse_day_format2(int day, std::string& res)
{
    res.push_back(static_cast<char>(day / 10 + '0'));
    res.push_back(static_cast<char>(day % 10 + '0'));
}

inline void parse_day_format3(int year, int month, int day, std::string& res)
{
    int w = cal_weekday(year, month, day);
    if (w == -1) throw DateTimeExcep{ "illegal date: the input date must be more than " + TLDate };
    res += DayAbbreFull[w].first;
}

inline void parse_day_format4(int year, int month, int day, std::string& res)
{
    int w = cal_weekday(year, month, day);
    if (w == -1) throw DateTimeExcep{ "illegal date: the input date must be more than " + TLDate };
    res += DayAbbreFull[w].second;    
}

/**
 * @brief 从 pos 开始解析 format，直到下一字符不是 DayFormat
 * @param pos the start index of parsing format, return the end index of parsing format
 * @param res 解析的结果添加到 res 末尾
 */
inline void parse_day_format(int& pos, const std::string& format, int year, int month, int day, std::string& res)
{
    auto len = format.size();
    int cnt = 0;
    for (; pos < len; ++pos) {
        if (format[pos] != details::DayFormat) {
            --pos; 
            break;
        }
        ++cnt;
        if (cnt == 4) {
            parse_day_format4(year, month, day, res);
            cnt = 0;
        }
    }
    if (cnt == 3) {
        cnt = 0;
        parse_day_format3(year, month, day, res);
    }
    else if (cnt == 2) {
        cnt = 0;
        parse_day_format2(day, res);
    }
    else if (cnt == 1) {
        cnt = 0;
        parse_day_format1(day, res);
    }
    if (cnt != 0) throw DateTimeExcep{ "illegal date-format: wrong DayFormat count" };
}

}   // namespace details


class Date
{
public:
    static void now(int& year, int& month, int& day) { details::get_now_date(year, month, day); }

public:
    Date();     // default: current date
    Date(int year, int month, int day);
    ~Date() = default;

public:
    int year() const { return _year; }
    int month() const { return _month; }
    int day() const { return _day; }

public:
    // ====================================================
    // [role]: 区分大小写、最长匹配原则
    // 1. year:
    //      (1)   YY: 只取年份后两位 (2025 ->   25)
    //      (2) YYYY: 保留年份所有位 (2025 -> 2025)
    // 2. month:
    //      (1)    M:  1 ~ 12
    //      (2)   MM: 01 ~ 12
    //      (3)  MMM: 月份英文缩写
    //      (4) MMMM: 月份英文全称
    // 3. day:
    //      (1)    D:  1 ~ 31
    //      (2)   DD: 01 ~ 31
    //      (3)  DDD: 星期几英文缩写
    //      (4) DDDD: 星期几英文全称
    // // 4. escape ch: '&' 
    // ====================================================
    std::string to_string(const std::string& format = "YYYY-MM-DD"); 

private:
    int _year;
    int _month;
    int _day;
};

Date::Date()
{
    now(_year, _month, _day);
}

Date::Date(int year, int month, int day)
    :_year{ year }, _month{ month }, _day{ day }
{
    if (year <= 0) throw DateTimeExcep{ "illegal year: " + std::to_string(year) };
    if (month <= 0 || month > 12) throw DateTimeExcep{ "illegal month: " + std::to_string(month) };
    if (day <= 0 || day > 31) throw DateTimeExcep{ "illegal day: " + std::to_string(day) };
    if (month == 2) {
        int max = details::DayPerMonth[month - 1];
        if (details::is_leap_year(year)) ++max;
        if (day > max) 
            throw DateTimeExcep{ "illegal " + details::MonthAbbreFull[month - 1].second + "'s day: " + std::to_string(day) };
    }
    if (day > details::DayPerMonth[month - 1]) 
        throw DateTimeExcep{ "illegal " + details::MonthAbbreFull[month - 1].second + "'s day: " + std::to_string(day) };
}

std::string Date::to_string(const std::string& format)
{
    int index = 0;
    auto len = format.size();
    std::string res;
    for (; index < len; ++index) {
        if (format[index] == details::YearFormat) details::parse_year_format(index, format, _year, res);
        else if (format[index] == details::MonthFormat) details::parse_month_format(index, format, _month, res);
        else if (format[index] == details::DayFormat) details::parse_day_format(index, format, _year, _month, _day, res);
        else res.push_back(format[index]);
    }
    return res;
}

}   // namespace header
